﻿using System;
using System.Text;
using System.Drawing;
using System.Collections.Generic;
using ADV_Evaluator;
using ADV_Evaluator.Data;
using ADV_Evaluator.Command;


namespace ADV_Editor {

    /// <summary>
    /// commandListの項目として使うデータ構造体。
    /// </summary>
    struct CommandListData {

        /// <summary>
        /// コマンド名の最初に付ける記号
        /// </summary>
        const string MarkString = "◆";
        /// <summary>
        /// コマンド名とその後ろの区切りに使う文字列
        /// </summary>
        const string SplitString = " ： ";

        /// <summary>
        /// コマンドの種類の列挙体
        /// </summary>
        private CommandData data;
        /// <summary>
        /// コマンドをListBoxに描く際に使うブラシ
        /// </summary>
        private Brush brush;
        /// <summary>
        /// コマンドをListBoxに描く際に使う、コマンド名のうしろに表示する属性値を集めて加工した文字列
        /// </summary>
        private string listText;
        /// <summary>
        /// CommandText + listText。ToStringで返す、このデータの文字列形式。
        /// </summary>
        private string text;


        /// <summary>
        /// 元となるコマンドデータを指定して初期化
        /// </summary>
        /// <param name="data"></param>
        public CommandListData(CommandData data) {
            this.data = data;
            this.listText = string.Empty;
            this.text = string.Empty;
            this.brush = Brushes.Black;
            SetListText();
            SetBrush();
            SetText();
        }
        /// <summary>
        /// コマンド名と属性値を指定して初期化
        /// </summary>
        /// <param name="type"></param>
        /// <param name="param"></param>
        public CommandListData(CommandName type, ICommandParams param)
            : this(new CommandData(type, param)) {
        }
        /// <summary>
        /// 2行目以降の行を表すときに使うコンストラクタ
        /// </summary>
        /// <param name="type">コマンド名</param>
        /// <param name="listText">その行のテキスト</param>
        public CommandListData(CommandName type, string listText) {
            this.data = new CommandData(type, null);
            this.listText = listText;
            this.text = string.Empty;
            this.brush = Brushes.Black;
            SetBrush();
            SetText();
        }


        /// <summary>
        /// コマンド名、属性に従ってlistTextを設定
        /// </summary>
        private void SetListText() {
            // コマンド名で分岐
            switch (data.Name) {
            case CommandName.文章の表示: 
                {
                    MessageParams param = (MessageParams)data.Params;
                    listText = param.Text;
                }
                break;
            case CommandName.文章オプションの変更:
                listText = "";
                break;
            case CommandName.選択肢の表示:
                {
                    ChoiceParams param = (ChoiceParams)data.Params;
                    listText = string.Join(", ", param.Options);
                }
                break;
            case CommandName.選択肢: 
                {
                    ChoiceOptionParams param = (ChoiceOptionParams)data.Params;
                    listText = string.Format("[{0}] の場合", param.Text);
                }
                break;
            case CommandName.変数の操作: 
                {
                    VariableParams param = (VariableParams)data.Params;
                    string operation = "";
                    switch (param.Operation) {
                    case VariableOperation.代入: operation = "="; break;
                    case VariableOperation.加算: operation = "+="; break;
                    case VariableOperation.減算: operation = "-="; break;
                    case VariableOperation.乗算: operation = "*="; break;
                    case VariableOperation.除算: operation = "/="; break;
                    case VariableOperation.剰余: operation = "%="; break;
                    case VariableOperation.累乗: operation = "**="; break;
                    }
                    string operand = "";
                    switch(param.OperandType){
                    case VariableOperand.定数:
                        operand = ((short)param.OperandValue).ToString();
                        break;
                    case VariableOperand.変数:
                        operand = (string)param.OperandValue;
                        break;
                    case VariableOperand.乱数:
                        short[] range = (short[])param.OperandValue;
                        operand = string.Format("{0} ～ {1}", range[0], range[1]);
                        break;
                    }
                    listText = param.Name + " " + operation + " " + operand;
                }
                break;
            case CommandName.条件分岐: 
                { 
                    BranchParams param = (BranchParams)data.Params;
                    listText = string.Format("{0} {1} {2}", param.VariableName, param.Operator, param.Value);        
                }
                break;
            case CommandName.ループ: 
                {
                    LoopParams param = (LoopParams)data.Params;
                    if (param.HasCondition) {
                        listText = string.Format("{0} {1} {2} の間繰り返し", param.VariableName, param.Operator, param.Value);
                    } else {
                        listText = "";
                    }
                }
                break;
            case CommandName.ラベルの定義:
            case CommandName.ラベルに飛ぶ: 
                {
                    LabelParams param = (LabelParams)data.Params;
                    listText = param.Name;
                }
                break;
            case CommandName.シナリオの移動: 
            case CommandName.シナリオを埋め込む:
                {
                    ScenarioParams param = (ScenarioParams)data.Params;
                    listText = param.FileName;
                }
                break;
			case CommandName.クリッカブル領域の配置:
				{
					ClickableMapParams param = (ClickableMapParams)data.Params;
					listText = param.Label;
					if (param.Mode.Equals(ClickableMapMode.Scenario))
					{ listText += ("、シナリオ:" + param.ScenarioFileName); }
					if (param.Mode.Equals(ClickableMapMode.Label))
					{ listText += ("、ラベル:" + param.ScenarioFileName); }
					if (param.DeleteMode.Equals(DeleteClickableMapMode.Single))
					{ listText += ("、削除動作:" + param.DeleteLabel); }
					if (param.DeleteMode.Equals(DeleteClickableMapMode.All))
					{ listText += ("、削除動作:全て"); }
				}
				break;
			case CommandName.クリッカブル領域の削除:
				{
					DeleteClickableMapParams param = (DeleteClickableMapParams)data.Params;
					if (param.Mode.Equals(DeleteClickableMapMode.Single))
					{ listText = param.Label; }
					if (param.Mode.Equals(DeleteClickableMapMode.All))
					{ listText = "全て"; }
				}
				break;
			case CommandName.立ち絵の表示: 
                {
                    ImageParams param = (ImageParams)data.Params;
                    listText += (param.FileName + "、");
                    switch (param.Label) {
                    case "__left__": listText += "左"; break;
                    case "__center__": listText += "中央"; break;
                    case "__right__": listText += "右"; break;
                    default:
                        listText += string.Format("{0}、({1}, {2})", param.Label, param.X, param.Y);
                        break;
                    }
                    listText += ("、レイヤー：" + param.Z.ToString());
                    if (param.Duration > 0) {
                        listText += ("、" + param.Duration + "フレーム");
                    }
                }
                break;
            case CommandName.立ち絵の消去: 
                {
                    ImageParams param = (ImageParams)data.Params;
                    switch (param.Label) {
                    case "__left__": listText += "左"; break;
                    case "__center__": listText += "中央"; break;
                    case "__right__": listText += "右"; break;
                    default: 
                        listText += param.Label;
                        break;
                    }
                    listText += ("、レイヤー：" + param.Z.ToString());
                    if (param.Duration > 0)
                    {
                        listText += ("、" + param.Duration + "フレーム");
                    }
                }
                break;
			case CommandName.背景画像の表示:
				{
					BgImageParams param = (BgImageParams)data.Params;
					listText = param.FileName;
					if (param.Duration > 0)
					{
						listText += ("、" + param.Duration + "フレーム");
					}
					if (param.Z.Equals(0)) { listText += ("、レイヤー：Back"); }
					if (param.Z.Equals(1)) { listText += ("、レイヤー：Front"); }
				}
				break;
            case CommandName.背景画像の消去: 
                {
                    BgImageParams param = (BgImageParams)data.Params;
                    if (param.Duration > 0) {
                        listText += (param.Duration + "フレーム");
                    }
					if (param.Z.Equals(0)) { listText += ("、レイヤー：Back"); }
					if (param.Z.Equals(1)) { listText += ("、レイヤー：Front"); }
				}
                break;
			case CommandName.背景色の変更:
				{
					BackColorParams param = (BackColorParams)data.Params;
					if (param.Duration > 0)
					{
						listText += (param.Duration + "フレーム");
					}
				}
				break;
			case CommandName.トランジション準備:
                break;
            case CommandName.トランジション実行: 
                {
                    TransitionParams param = (TransitionParams)data.Params;
                    listText = param.Duration + "フレーム";
                }
                break;
            case CommandName.ＢＧＭの演奏: 
                {
                    BgmParams param = (BgmParams)data.Params;
                    listText = param.FileName;
                }
                break;
            case CommandName.ＢＧＭの停止:
                break;
            case CommandName.ＳＥの演奏: 
                {
                    SeParams param = (SeParams)data.Params;
                    listText = param.FileName;
                }
                break;
            case CommandName.ＳＥの停止:
                break;
            case CommandName.ウェイト: 
                {
                    WaitParams param = (WaitParams)data.Params;
                    switch(param.Mode){
                    case WaitMode.Frame:
                        listText = param.Duration + "フレーム";
                        break;
                    case WaitMode.WaitKey:
                        listText = "キーが押されるまで";
                        break;
					case WaitMode.WaitClickable:
						listText = "クリッカブル領域が動作するまで";
						break;
					}
                }
                break;
            case CommandName.注釈: 
                {
                    NoteParams param = (NoteParams)data.Params;
                    listText = param.Text;
                }
                break;
            }
        }

        /// <summary>
        /// コマンド名に応じてブラシを設定
        /// </summary>
        private void SetBrush() {
            switch (data.Name) {
            case CommandName.文章オプションの変更:
                brush = Brushes.Gray;
                break;
            case CommandName.注釈:
                brush = Brushes.Green;
                break;
            case CommandName.変数の操作:
                brush = Brushes.Red;
                break;
            case CommandName.選択肢の表示:
            case CommandName.選択肢:
            case CommandName.条件分岐:
            case CommandName.それ以外の場合:
            case CommandName.分岐終了:
            case CommandName.ループ:
            case CommandName.ループを抜ける:
            case CommandName.以上繰り返し:
            case CommandName.ラベルの定義:
            case CommandName.ラベルに飛ぶ:
                brush = Brushes.Blue;
                break;
            case CommandName.シナリオの移動:
            case CommandName.シナリオを埋め込む:
                brush = Brushes.Purple;
                break;
			case CommandName.クリッカブル領域の配置:
			case CommandName.クリッカブル領域の削除:
			case CommandName.立ち絵の表示:
            case CommandName.立ち絵の消去:
            case CommandName.背景画像の表示:
            case CommandName.背景画像の消去:
			case CommandName.背景色の変更:
				brush = Brushes.DarkRed;
                break;
            case CommandName.トランジション準備:
            case CommandName.トランジション実行:
            case CommandName.ウェイト:
                brush = Brushes.DarkGoldenrod;
                break;
            case CommandName.ＢＧＭの演奏:
            case CommandName.ＢＧＭの停止:
            case CommandName.ＳＥの演奏:
            case CommandName.ＳＥの停止:
                brush = Brushes.DarkCyan;
                break;
            case CommandName.文章の表示:        
            default:
                brush = Brushes.Black;
                break;
            }
        }

        /// <summary>
        /// ◆コマンド名 : 値 　形式の文字列を返す
        /// </summary>
        /// <returns></returns>
        public new string ToString() {
            return text;
        }
        /// <summary>
        /// this.textの値を設定
        /// </summary>
        private void SetText() {
            StringBuilder sb = new StringBuilder();
            // インデント
            for (int i = 0; i < data.Indent; i++) {
                sb.Append(" 　");
            }
            sb.Append(CommandText);
            if (listText.Length > 0) {
                sb.Append(FirstLine);
            }
            text = sb.ToString();
        }

        /// <summary>
        /// コマンド名の部分を文字列化したもの
        /// </summary>
        public string CommandText {
            get {
                StringBuilder sb = new StringBuilder();
                switch(data.Name){
                // 空行は◆だけ
                case CommandName.無し:
                    sb.Append(MarkString);
                    break;
                // 分岐終了は◆をつけない
                case CommandName.それ以外の場合:
                case CommandName.分岐終了:
                case CommandName.以上繰り返し:                
                    sb.Append(SplitString + data.Name.ToString());
                    break;
                // 選択肢はコマンド名をつけずテキストのみ
                case CommandName.選択肢:
                    sb.Append(SplitString);
                    break;
                default:
                    if (IsSelectable) {
                        sb.Append(MarkString);
                        sb.Append(data.Name.ToString());
                        if (listText.Length > 0) {
                            sb.Append(SplitString);
                        }
                    // 2行目以降はコマンド名を抜いて返す
                    } else {
                        // 実際にどれだけずれているのかを計測して合わせてみる
                        using (Bitmap bitmap = new Bitmap(1, 1))
                        using (Font font = new Font(FontFamily.GenericSerif, 9.75f))
                        using (Graphics g = Graphics.FromImage(bitmap)) {
                            float indentWidth = g.MeasureString((MarkString + data.Name.ToString() + SplitString), font).Width;
                            while(g.MeasureString(sb.ToString(), font).Width < indentWidth) {
                                sb.Append('．');
                            }
                        }
                        // 酷い苦肉の策。"．"と"　"は大体同じ幅らしい。
                        // けっこうぴったりきます。
                        sb.Replace('．', '　').Append(' '); 
                    }
                    break;
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// 選択可能な行であるかどうか返す。
        /// 普通のコマンドのほか、複数行にわたるコマンドの1行目や、空行であればtrueを返す。
        /// </summary>
        public bool IsSelectable {
            get {
                return (
                    data.Params != null &&
                    data.Name != CommandName.それ以外の場合 &&
                    data.Name != CommandName.分岐終了 &&
                    data.Name != CommandName.以上繰り返し &&
                    data.Name != CommandName.選択肢
                );
            }
        }
        /// <summary>
        /// ListTextが複数行のときtrueを返す。
        /// </summary>
        public bool IsMultiline {
            get { return listText.Contains("\n"); }
        }
        /// <summary>
        /// ListTextの最初の行
        /// </summary>
        public string FirstLine {
            get { return listText.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)[0]; }
        }
        /// <summary>
        /// ListTextの2行目以降の行を集めた配列
        /// </summary>
        public string[] RestLines {
            get {
                string[] lines = listText.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
                string[] result = new string[lines.Length - 1];
                for (int i = 1; i < lines.Length; i++) {
                    result[i - 1] = lines[i];
                }
                return result;
            }
        }
        /// <summary>
        /// このコマンドが文章の表示等の2行目以降のコマンドならばtrueを返す。
        /// このプロパティが偽であるコマンドがファイルに保存される。
        /// </summary>
        public bool IsRestLine {
            get { return data.Params == null; }
        }
        /// <summary>
        /// 引数のデータがRestLineに含まれていてコマンド名も一緒なら真を返す
        /// </summary>
        /// <param name="child"></param>
        /// <returns></returns>
        public bool IsParent(CommandListData child) {
            if (!child.IsRestLine) {
                return false;
            }
            if (this.Name != child.Name) {
                return false;
            }
            foreach (string line in this.RestLines) {
                if (line == child.listText) {
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// 描画に用いるブラシ
        /// </summary>
        public Brush Brush {
            get { return this.brush; }
        }
        /// <summary>
        /// 元データ
        /// </summary>
        public CommandData Data {
            get { return data; }
        }
        /// <summary>
        /// コマンド名
        /// </summary>
        public CommandName Name {
            get { return data.Name; }
        }
        /// <summary>
        /// コマンドの引数
        /// </summary>
        public ICommandParams Params {
            get { return data.Params; }
        }
        /// <summary>
        /// インデント
        /// </summary>
        public byte Indent {
            get { return data.Indent; }
            set { 
                data.Indent = value;
                SetText();
            }
        }

    }
}
